package com.wsoft.oss.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.file.FileReader;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.wsoft.constant.redis.RedisKeyConstant;
import com.wsoft.core.exception.Asserts;
import com.wsoft.core.service.RedisService;
import com.wsoft.oss.entity.OssFileEntity;
import com.wsoft.oss.enums.BucketNameEnum;
import com.wsoft.oss.enums.FileSizeEnum;
import com.wsoft.oss.enums.FileUpdateStatusEnum;
import com.wsoft.oss.manager.OssFileManager;
import com.wsoft.oss.query.MergePartQuery;
import com.wsoft.oss.query.PartUploadQuery;
import com.wsoft.oss.query.SecondUploadQuery;
import com.wsoft.oss.service.IOssFileService;
import com.wsoft.oss.service.IOssService;
import com.wsoft.oss.utils.OssUtil;
import com.wsoft.oss.vo.FileUploadVO;
import com.wsoft.utils.FileUtil;
import io.minio.GetObjectResponse;
import net.coobird.thumbnailator.Thumbnails;
import org.springframework.core.io.InputStreamResource;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @Author z001
 * @Date 2024/9/26 10:39
 */
public class OssLocalServiceImpl implements IOssService {

    /**
     * 本地路径
     */
    private final String uploadPath;
    /**
     * local
     */
    private final String storageType;
    /**
     * 文件上传大小
     */
    private final Long maxSize;
    @Resource
    private RedisService redisService;
    @Resource
    private OssFileManager ossFileManager;
    @Resource
    private IOssFileService ossFileService;

    public OssLocalServiceImpl(String uploadPath, String storageType, Long maxSize) {
        this.uploadPath = uploadPath;
        this.storageType = storageType;
        this.maxSize = maxSize;
    }

    /**
     * 分片上传
     *
     * @param uploadQuery
     */
    @Override
    public void uploadPart(PartUploadQuery uploadQuery) throws IOException {
        Boolean b = redisService.sIsMember(RedisKeyConstant.Oss.OSS_PART_INDEX + uploadQuery.getMd5(), uploadQuery.getUploadPartCount());
        if (b) {
            //如果已经上传过 则不继续上传
            return;
        }
        Long fileMaxSize =  maxSize;
        long sizeKb = uploadQuery.getFilePart().getSize() / (FileUtil.MB);
        if(null != fileMaxSize){
            Object object = redisService.get(RedisKeyConstant.Oss.OSS_FILE_SIZE + uploadQuery.getMd5());
            Long fileSizeObj = object == null? 0L :  Long.valueOf(object.toString());
            if((fileSizeObj + sizeKb)/FileUtil.MB > fileMaxSize){
                //文件超出最大限制
                Asserts.fail("[oss.file.maximum.limit]");
            }
        }


        //临时地址存放目录
        String temporaryPath = OssUtil.buildLocalDirTemporaryPath(uploadPath, uploadQuery.getBucketType(), uploadQuery.getMd5());
        File directory = new File(temporaryPath);
        if (!directory.exists()) {
            // 目录不存在，新建
            directory.mkdirs();
        }

        MultipartFile filePart = uploadQuery.getFilePart();
        String newFileName = uploadQuery.getFileName() + OssUtil.PART + uploadQuery.getUploadPartCount();
        String path = temporaryPath + newFileName;
        File fileTemp = new File(new File(path).getAbsolutePath());
        filePart.transferTo(fileTemp);

        //赋值redis 上传分片的数量
        redisService.sAdd(RedisKeyConstant.Oss.OSS_PART_INDEX + uploadQuery.getMd5(), uploadQuery.getUploadPartCount());
        redisService.expire(RedisKeyConstant.Oss.OSS_PART_INDEX + uploadQuery.getMd5(), RedisKeyConstant.Oss.EXPIRED);
        redisService.sAdd(RedisKeyConstant.Oss.OSS_FILE_NAME + uploadQuery.getMd5(), newFileName);
        redisService.expire(RedisKeyConstant.Oss.OSS_FILE_NAME + uploadQuery.getMd5(), RedisKeyConstant.Oss.EXPIRED);
        redisService.incr(RedisKeyConstant.Oss.OSS_FILE_SIZE + uploadQuery.getMd5(),sizeKb );
        redisService.expire(RedisKeyConstant.Oss.OSS_FILE_SIZE + uploadQuery.getMd5(), RedisKeyConstant.Oss.EXPIRED);
    }

    /**
     * 秒传
     *
     * @param query
     * @return
     */
    @Override
    public FileUploadVO secondUpload(SecondUploadQuery query) {
        FileUploadVO vo = new FileUploadVO();
        OssFileEntity ossFile = ossFileManager.getMd5(query.getMd5());
        if (ossFile != null) {
            String newUuid = OssUtil.getUuid();
            String bucketName = BucketNameEnum.resolve(query.getBucketType()).getBucketName();

            //拷贝文件
            String sourceFilePath = ossFile.getUrl();
            // 目标目录路径
            String targetDirPath = OssUtil.buildLocalDirPath(uploadPath, query.getBucketType());
            // 创建 File 对象
            File sourceFile = new File(sourceFilePath);
            File targetDir = new File(targetDirPath);

            // 确保目标目录存在
            if (!targetDir.exists()) {
                targetDir.mkdirs();
            }

            // 构建目标文件路径
            String targetFilePath = targetDirPath + OssUtil.buildLocalFileName(newUuid, ossFile.getFileSuffix());
            File targetFile = new File(targetFilePath);
            // 复制文件
            FileUtil.copy(sourceFile, targetFile, true);

            //赋值 新属性
            OssFileEntity newFile = new OssFileEntity();
            newFile.setFileType(ossFile.getFileType());
            newFile.setFileSuffix(ossFile.getFileSuffix());
            newFile.setStorageType(ossFile.getStorageType());
            newFile.setName(query.getFileName());
            newFile.setSize(ossFile.getSize());
            newFile.setUuid(Long.valueOf(newUuid));
            newFile.setUrl(targetFilePath);
            newFile.setBucketName(bucketName);
            //秒传文件 这边不赋值 md5
            ossFileManager.save(newFile);

            //判断存在 则直接显示上传成功,并返回id
            vo.setFileUpdateStatus(FileUpdateStatusEnum.UPLOADED.getType());
            vo.setId(newFile.getId());
            vo.setFileName(newFile.getName());
        } else {
            //文件不存在,不能秒传
            Asserts.fail("[oss.file.not.exist.not.second.upload]");
        }
        return vo;
    }

    @Override
    public FileUploadVO mergePart(MergePartQuery uploadQuery) {
        String md5 = uploadQuery.getMd5();
        String bucketName = BucketNameEnum.resolve(uploadQuery.getBucketType()).getBucketName();
        //分片 临时地址
        String temporaryPath = OssUtil.buildLocalDirTemporaryPath(uploadPath, uploadQuery.getBucketType(), md5);
        // 合并后的文件路径
        String mergedFilePath = OssUtil.buildLocalDirPath(uploadPath, uploadQuery.getBucketType());
        Set<Object> partNameSet = redisService.sMembers(RedisKeyConstant.Oss.OSS_FILE_NAME + md5);
        if(CollUtil.isEmpty(partNameSet)){
            Asserts.fail("[oss.file.part.not.exist]");
        }
        //返回值
        FileUploadVO vo = new FileUploadVO();
        try {
            File directory = new File(mergedFilePath);
            if (!directory.exists()) {
                // 目录不存在，新建
                directory.mkdirs();
            }
            String newFileName = OssUtil.getPartName(partNameSet);
            String fileType = FileUtil.getExtensionName(newFileName);
            String uuid = OssUtil.getUuid();

            mergeFiles(temporaryPath, mergedFilePath + OssUtil.buildLocalFileName(uuid, fileType));
            //删临时文件
            FileUtil.del(temporaryPath);
            Object fileKbSize = redisService.get(RedisKeyConstant.Oss.OSS_FILE_SIZE + md5);

            //删redis
            redisService.del(RedisKeyConstant.Oss.OSS_PART_INDEX + md5);
            redisService.del(RedisKeyConstant.Oss.OSS_FILE_SIZE + md5);
            redisService.del(RedisKeyConstant.Oss.OSS_FILE_NAME + md5);

            //新增file表
            OssFileEntity ossFileEntity = new OssFileEntity();
            ossFileEntity.setUuid(Long.valueOf(uuid));
            ossFileEntity.setFileType(FileUtil.getFileType(fileType));
            ossFileEntity.setFileSuffix(fileType);
            ossFileEntity.setName(newFileName);
            ossFileEntity.setUrl(mergedFilePath + OssUtil.changeNewFileName(newFileName, uuid));
            ossFileEntity.setBucketName(bucketName);
            ossFileEntity.setStorageType(storageType);
            ossFileEntity.setMd5(md5);
            ossFileEntity.setSize(new BigDecimal(fileKbSize.toString()));
            ossFileManager.save(ossFileEntity);

            vo.setId(ossFileEntity.getId());
            vo.setFileName(newFileName);

        } catch (IOException e) {
            e.printStackTrace();
        }
        return vo;
    }

    @Override
    public byte[] downloadPart(OssFileEntity fileEntity, Integer picType, String range, HttpServletResponse response) throws IOException {
        if(null == fileEntity){
            Asserts.fail("[oss.file.not.exist]");
        }
        String url = OssUtil.getFileUrl(fileEntity, picType);
        File file = new File(url);

        //计算起始和结束位置
        Map<String, Long> sizeMap = OssUtil.buildSize(range,  file.length());
        long startByte = sizeMap.get("startByte");
        long endByte = sizeMap.get("endByte");

        // buffer 写入流
        BufferedOutputStream os = null;
        // minio 文件流
        GetObjectResponse stream = null;

        // 要下载的长度
        // 确保返回的 contentLength 不会超过文件的实际剩余大小
        long contentLength = Math.min(endByte - startByte,  file.length() - startByte);
        if(contentLength <= 0){
            return new byte[]{};
        }
        try {
            // 获取文件流
            os = new BufferedOutputStream(response.getOutputStream());
            // 将读取的文件写入到 OutputStream
            byte[] bytes = new byte[1024 * 64];
            long bytesWritten = 0;
            int bytesRead = -1;
            while ((bytesRead = new FileInputStream(file).read(bytes)) != -1) {
                if (bytesWritten + bytesRead >= contentLength) {
                    os.write(bytes, 0, (int)(contentLength - bytesWritten));
                    break;
                } else {
                    os.write(bytes, 0, bytesRead);
                    bytesWritten += bytesRead;
                }
            }
            os.flush();
            response.flushBuffer();
            // 返回对应http状态
            return bytes;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (os != null) {
                os.close();
            }
            if (stream != null) {
                stream.close();
            }
        }
        return null;

    }

    @Override
    public boolean deleteFile(Long fileId) throws Exception {

        OssFileEntity fileEntity = ossFileManager.getById(fileId);
        if(ObjUtil.isEmpty(fileEntity)){
            return true;
        }
        boolean b = ossFileManager.removeById(fileId);
        if(b){
            FileUtil.del(fileEntity.getUrl());
        }
        return b;
    }

    @Override
    public Map<String,String> preview(Long fileId,String sessionId) throws Exception {
        OssFileEntity fileEntity = ossFileManager.getById(fileId);
        //校验session
        ossFileService.checkSessionId(sessionId,fileEntity.getBucketName());

        // 创建 File 对象
        File file = new File(fileEntity.getUrl());
        // 创建 FileInputStream 获取输入流
        InputStream inputStream = Files.newInputStream(file.toPath());
        Map<String,String> map = new HashMap<>(2);
        map.put("pic",FileUtil.convertBase64(inputStream));
        map.put("fileSuffix",fileEntity.getFileSuffix());
        return map;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<FileUploadVO> uploadList(MultipartFile[] multipartFileList, Integer bucketType) throws Exception {
        Long fileMaxSize = maxSize;
        //校验文件大小
        OssUtil.checkFileSize(multipartFileList,fileMaxSize);

        List<FileUploadVO> voList = new ArrayList<>();
        List<OssFileEntity> fileList = new ArrayList<>();
        for (MultipartFile multipartFile : multipartFileList) {
            //上传
            OssFileEntity fileEntity = this.upload(multipartFile, bucketType);
            fileList.add(fileEntity);
        }
        ossFileManager.saveBatch(fileList);
        for (OssFileEntity ossFileEntity : fileList) {
            FileUploadVO vo = new FileUploadVO();
            vo.setId(ossFileEntity.getId());
            vo.setFileName(ossFileEntity.getName());
            voList.add(vo);
        }
        return voList;
    }

    /**
     * 返回文件 InputStreamResource
     *
     * @param ossFile
     * @return
     */
    @Override
    public InputStreamResource getInputStream(OssFileEntity ossFile, Integer picType) throws IOException {
        boolean exist = FileUtil.exist(ossFile.getUrl());
        if (!exist) {
            Asserts.fail("[oss.file.not.exist]");
        }
        FileReader fileReader = null;
        //非图片类型 直接返回
        if (!ossFile.getFileType().equals(FileUtil.IMAGE) || null == FileSizeEnum.resolve(picType)) {
            fileReader = new FileReader(ossFile.getUrl());
            return new InputStreamResource(fileReader.getInputStream());
        }
        String url = ossFile.getUrl();
        //图片类型
        if (FileSizeEnum.MIN.getValue().equals(picType)) {
            if (StrUtil.isBlank(ossFile.getMinUrl())) {
                this.changPicSize(ossFile, picType);
            }
            url = ossFile.getMinUrl();
        } else if (FileSizeEnum.MID.getValue().equals(picType)) {
            if (StrUtil.isBlank(ossFile.getMidUrl())) {
                this.changPicSize(ossFile, picType);
            }
            url = ossFile.getMidUrl();
        } else if (FileSizeEnum.MAX.getValue().equals(picType)) {
            if (StrUtil.isBlank(ossFile.getMaxUrl())) {
                this.changPicSize(ossFile, picType);
            }
            url = ossFile.getMaxUrl();
        }
        fileReader = new FileReader(url);
        return new InputStreamResource(fileReader.getInputStream());
    }


    // ################################## 私有方法  ##################################

    /**
     * 上传单个文件
     *
     * @param multipartFile
     * @param bucketType
     * @return
     * @throws Exception
     */
    private OssFileEntity upload(MultipartFile multipartFile, Integer bucketType) throws Exception {
        String dirPath = OssUtil.buildLocalDirPath(uploadPath, bucketType);
        File directory = new File(dirPath);
        if (!directory.exists()) {
            // 目录不存在，新建
            directory.mkdirs();
        }
        String uuid = OssUtil.getUuid();
        String newFileName = OssUtil.changeNewFileName(multipartFile.getOriginalFilename(), uuid);
        String filePath = dirPath + newFileName;
        File fileTemp = new File(new File(filePath).getAbsolutePath());


        String fileType = FileUtil.getExtensionName(multipartFile.getOriginalFilename());
        OssFileEntity ossFileEntity = new OssFileEntity();
        ossFileEntity.setUuid(Long.valueOf(uuid));
        ossFileEntity.setFileType(FileUtil.getFileType(fileType));
        ossFileEntity.setFileSuffix(fileType);
        ossFileEntity.setName(multipartFile.getOriginalFilename());
        ossFileEntity.setSize(FileUtil.getKbSize(multipartFile.getSize()));
        ossFileEntity.setUrl(filePath);
        ossFileEntity.setBucketName(BucketNameEnum.resolve(bucketType).getBucketName());
        ossFileEntity.setStorageType(storageType);

        //保存文件
        multipartFile.transferTo(fileTemp);

        return ossFileEntity;
    }

    /**
     * 修改图片大小 并上传
     *
     * @param ossFile
     * @param picType 大中小图
     * @throws IOException
     */
    private void changPicSize(OssFileEntity ossFile, Integer picType) throws IOException {
        //获取文件流
        BufferedInputStream inputStream = FileUtil.getInputStream(new File(ossFile.getUrl()));
        if (FileSizeEnum.MIN.getValue().equals(picType) && StrUtil.isBlank(ossFile.getMinUrl())) {
            ossFile.setMinUrl(resizeAndSaveImage(inputStream, ossFile.getName(), FileSizeEnum.MIN.getType(),
                    String.valueOf(ossFile.getUuid()), ossFile.getBucketName()));
            ossFileManager.updateById(ossFile);
        } else if (FileSizeEnum.MID.getValue().equals(picType) && StrUtil.isBlank(ossFile.getMidUrl())) {
            ossFile.setMidUrl(resizeAndSaveImage(inputStream, ossFile.getName(), FileSizeEnum.MID.getType(),
                    String.valueOf(ossFile.getUuid()), ossFile.getBucketName()));
            ossFileManager.updateById(ossFile);
        } else if (FileSizeEnum.MAX.getValue().equals(picType) && StrUtil.isBlank(ossFile.getMaxUrl())) {
            ossFile.setMaxUrl(resizeAndSaveImage(inputStream, ossFile.getName(), FileSizeEnum.MAX.getType(),
                    String.valueOf(ossFile.getUuid()), ossFile.getBucketName()));
            ossFileManager.updateById(ossFile);
        }
    }

    /**
     * 缩放图片并保存到指定文件夹
     *
     * @param multipartFile 上传的图片文件
     * @throws IOException 如果发生I/O错误
     */
    public String resizeAndSaveImage(InputStream is, String originalFilename, String type, String uuid, String bucketName) throws IOException {
        String targetDir = OssUtil.buildLocalDirPath(uploadPath, bucketName);
        // 确保目标文件夹存在
        File dir = new File(targetDir);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        String extension = FileUtil.getExtensionName(originalFilename);
        String targetName = OssUtil.buildChangePicName(uuid, type, extension);

        // 由于Thumbnails.of()通常不支持InputStream直接作为输入，我们将其写入到临时文件
        File tempFile = File.createTempFile(OssUtil.TEMPORARY + StrUtil.UNDERLINE + type, StrUtil.DOT + extension);
        try (InputStream inputStream = is;
             FileOutputStream outputStream = new FileOutputStream(tempFile)) {

            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }

            FileSizeEnum sizeEnum = FileSizeEnum.resolve(type);
            //使用Thumbnails来缩放tempFile
            File targetFile = new File(dir, targetName);
            Thumbnails.of(tempFile)
                    .size(sizeEnum.getWidth(), sizeEnum.getHeight())
                    .toFile(targetFile);

        } finally {
            // 清理临时文件
            if (!tempFile.delete()) {
                tempFile.deleteOnExit(); // 如果删除失败，则尝试在JVM退出时删除
            }
        }
        return targetDir + targetName;
    }

    /**
     * 合并文件
     *
     * @param directoryPath
     * @param mergedFilePath
     * @throws IOException
     */
    public static void mergeFiles(String directoryPath, String mergedFilePath) throws IOException {
        // 获取目录中的所有文件
        File directory = new File(directoryPath);
        File[] fileArr = directory.listFiles();
        if (fileArr.length == 0) {
            return;
        }
        List<File> files = Arrays.asList(fileArr);

        // 对文件进行排序，确保按顺序合并（假设文件名中的数字部分表示顺序）
        files.sort(Comparator.comparingInt(file -> {
            String fileName = file.getName();
            Pattern pattern = Pattern.compile(OssUtil.PART_PATTERN);
            Matcher matcher = pattern.matcher(fileName);
            matcher.find();
            return Integer.parseInt(matcher.group(1));
        }));

        // 创建输出流用于写入合并后的文件
        try (BufferedOutputStream mergedOutput = new BufferedOutputStream(new FileOutputStream(mergedFilePath))) {
            // 遍历每个分片文件并写入合并文件
            for (File file : files) {
                try (BufferedInputStream input = new BufferedInputStream(new FileInputStream(file))) {
                    byte[] buffer = new byte[1024];
                    int bytesRead;
                    while ((bytesRead = input.read(buffer)) != -1) {
                        mergedOutput.write(buffer, 0, bytesRead);
                    }
                }
            }
        }
    }

}
